/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C)N_H 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C)N_H 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.soundrenderer.logic;

import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.magiciansforest.audio.soundrenderer.ApplicationBase;
import com.magiciansforest.audio.soundrenderer.event.FramePositionChangeEvent;
import com.magiciansforest.audio.soundrenderer.event.FramePositionChangeEventListener;
import com.magiciansforest.audio.soundrenderer.logic.sound.AudioRenderThread;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author N_H
 */
public class TimeKeeper extends AbstractControl {

    public static final double MMD_FPS = 30;
    private static final float MAX_DIFFERENCE = 0.1f;
    private static TimeKeeper singleton;
    private Movie mov;
    private AudioRenderThread audioRender;
    private double time = 0;
    private double movieStartTime = -1;
    private List<FramePositionChangeEventListener> framePositionChangeListeners = new ArrayList<FramePositionChangeEventListener>();
    private List<Control> controls = new ArrayList<Control>();
    private boolean self = false;

    private TimeKeeper(ApplicationBase viewer, Movie mov, AudioRenderThread audioRender) {
        this.mov = mov;
        this.audioRender = audioRender;

        this.setEnabled(true);

        viewer.getRootNode().addControl(this);
    }
    
    public static TimeKeeper createTimeKeeper(ApplicationBase viewer, Movie mov, AudioRenderThread audioRender) {
        singleton = new TimeKeeper(viewer, mov, audioRender);
        return singleton;
    }
    
    public static TimeKeeper getTimeKeeper() {
        return singleton;
    }

    public void reset() {
        time = 0;
        audioRender.resetTime();
        movieStartTime = -1;
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }

    @Override
    protected void controlUpdate(float tpf) {
        if (mov.getMovieStatus() == Movie.STARTED || mov.getMovieStatus() == Movie.RENDERING) {
            if (movieStartTime < 0) {
                movieStartTime = time;
            }
            
            double progressTime = time - movieStartTime;
            
            float dt;
            if (audioRender.getRenderedTime() + MAX_DIFFERENCE < progressTime) { //too fast
                //speed down
                dt = tpf * 0.9f;
            } else if (audioRender.getRenderedTime() > progressTime) { //too slow
                //speed up
                dt = tpf * 1.1f;
            } else {
                dt = tpf;
            }
            
            for (Control control : controls) {
                control.update(dt);
            }
            
            updateTime(time + dt);
        } else {
            audioRender.resetTime();
            movieStartTime = -1;
        }
    }

    public Control cloneForSpatial(Spatial sptl) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public double getRenderedTime() {
        return audioRender.getRenderedTime();
    }

    public double getTime() {
        return time;
    }
    
    public double getProgressTime() {
        return time - movieStartTime;
    }

    public void setTime(double time) {
        if (!self) {
            self = true;
            this.time = time;
            audioRender.resetTime();
            movieStartTime = -1;
            fireFramePositionChangeEvent(new FramePositionChangeEvent(this, FramePositionChangeEvent.SET, getMovieFramePosition()));
            self = false;
        }
    }

    public int getMovieFramePosition() {
        return (int) Math.round(time * MMD_FPS);
    }

    public void setMovieFramePosition(int frameNo) {
        setTime((double) frameNo / MMD_FPS);
    }

    public long getSoundFramePosition(double samplingRate) {
        return Math.round(time * samplingRate);
    }

    public void addFramePositionChangeEventListener(FramePositionChangeEventListener listener) {
        framePositionChangeListeners.add(listener);
    }

    public void removeFramePositionChangeEventListener(FramePositionChangeEventListener listener) {
        framePositionChangeListeners.remove(listener);
    }

    public void addControl(Control control) {
        controls.add(control);
    }

    public void removeContorl(Control control) {
        controls.remove(control);
    }

    private void updateTime(double t) {
        double next;
        if (t > mov.getLastFrameNo() / MMD_FPS) {
            next = mov.getLastFrameNo() / MMD_FPS;
            mov.stopMovie();
        } else if (t < 0) {
            next = 0;
        } else {
            next = t;
        }

        this.time = next;
        fireFramePositionChangeEvent(new FramePositionChangeEvent(this, FramePositionChangeEvent.UPDATE, getMovieFramePosition()));
    }

    private void fireFramePositionChangeEvent(FramePositionChangeEvent e) {
        for (FramePositionChangeEventListener listener : framePositionChangeListeners) {
            listener.changeFramePosition(e);
        }
    }
}
